Skip to content

AI models show their provider's brand logo in the model picker - #407

Merged
rbuergi merged 2 commits into
mainfrom
feat/model-provider-icons
Jul 10, 2026
Merged

rbuergi merged 2 commits into
mainfrom
feat/model-provider-icons

Conversation

@rbuergi

@rbuergi rbuergi commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Problem

Every AI model rendered an identical sparkle in the picker: BuiltInLanguageModelProvider stamped Icon = "Sparkle" on every LanguageModel node and Icon = "Key" on every ModelProvider. Providers were indistinguishable at a glance.

The picker (MeshNodePickerControl) already renders each node's Icon (URL / data-URI / inline-SVG / emoji), so the fix is purely to give the nodes real icons — no picker changes.

What this does

  • 13 brand icons in src/MeshWeaver.Graph/Icons/ (embedded → served at /static/NodeTypeIcons/), built to match the existing icon design exactly: a brand-coloured rx=4 tile with a white glyph, so they're theme-independent by construction. Marks are from simple-icons (MIT), used nominatively.
  • ModelProviderIcons resolver — maps a model to its maker by substring, model id first, then provider name. That ordering means a claude-* served through AzureFoundry, or anthropic/claude-* via OpenRouter, still resolves to Anthropic. Unknown → null → falls back to the neutral sparkle. Lives in MeshWeaver.AI (not Graph) to keep the generic layer free of provider knowledge.
  • Wired into BuiltInLanguageModelProvider (the two icon assignments).
  • Brands: Anthropic, OpenAI, Google (Gemini/Gemma), Mistral, Meta (Llama), DeepSeek, xAI (Grok), Qwen, Perplexity, GitHub Copilot, OpenRouter, Ollama, Azure.

Tests

ModelProviderIconsTest — 23 cases: per-brand model-id mapping, the gateway case (AzureFoundry → Claude → Anthropic), OpenRouter-prefixed ids, provider-name fallback, and unknown → null. Green under -c Release -warnaserror.

Scope note

Covers the platform catalog (what fills the picker for everyone). BYO models a user creates via the generic create path aren't auto-stamped yet — the picker reads node.Icon, so wiring the same resolver into the "add your own provider" flow is a clean follow-up.

🤖 Generated with Claude Code

Every LanguageModel node carried a generic "Sparkle" icon (and every
ModelProvider a "Key"), so the picker rendered the same mark for every model.

Add per-provider brand icons: 13 tile SVGs (brand colour + white glyph,
matching the existing NodeType icon design) under MeshWeaver.Graph/Icons,
plus a ModelProviderIcons resolver that maps a model to its maker by the
model id first, then the provider name — so a Claude served through Azure
or OpenRouter still reads as Anthropic. Wired into BuiltInLanguageModelProvider;
unknown models fall back to the neutral sparkle. Adds a unit test and a
What's New entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the AI model picker UX by assigning provider/maker-specific brand icons to ModelProvider and LanguageModel catalog nodes, instead of showing the same generic icon for everything. It adds a small resolver in MeshWeaver.AI to infer the correct icon (model-id first, then provider name) and ships the corresponding SVG assets via the existing embedded NodeTypeIcons static content collection.

Changes:

  • Add ModelProviderIcons resolver to map model IDs / provider names to /static/NodeTypeIcons/{brand}.svg.
  • Wire the resolver into BuiltInLanguageModelProvider so seeded Provider/Model nodes get brand logos.
  • Add brand SVGs and a dedicated unit test suite for icon resolution, plus a “What’s New” entry.

Reviewed changes

Copilot reviewed 4 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/MeshWeaver.AI.Test/ModelProviderIconsTest.cs Adds unit coverage for icon inference (model-id precedence, gateway cases, unknown fallback).
src/MeshWeaver.AI/ModelProviderIcons.cs Introduces token-based resolver returning /static/NodeTypeIcons/*.svg paths for known brands.
src/MeshWeaver.AI/BuiltInLanguageModelProvider.cs Uses the resolver when emitting ModelProvider and LanguageModel catalog nodes.
src/MeshWeaver.Graph/Icons/anthropic.svg Adds Anthropic brand tile icon (embedded, served under /static/NodeTypeIcons/).
src/MeshWeaver.Graph/Icons/openai.svg Adds OpenAI brand tile icon.
src/MeshWeaver.Graph/Icons/google.svg Adds Google (Gemini/Gemma) brand tile icon.
src/MeshWeaver.Graph/Icons/mistral.svg Adds Mistral brand tile icon.
src/MeshWeaver.Graph/Icons/meta.svg Adds Meta (Llama) brand tile icon.
src/MeshWeaver.Graph/Icons/deepseek.svg Adds DeepSeek brand tile icon.
src/MeshWeaver.Graph/Icons/xai.svg Adds xAI (Grok) brand tile icon.
src/MeshWeaver.Graph/Icons/qwen.svg Adds Qwen brand tile icon.
src/MeshWeaver.Graph/Icons/perplexity.svg Adds Perplexity brand tile icon.
src/MeshWeaver.Graph/Icons/githubcopilot.svg Adds GitHub Copilot brand tile icon.
src/MeshWeaver.Graph/Icons/openrouter.svg Adds OpenRouter brand tile icon.
src/MeshWeaver.Graph/Icons/ollama.svg Adds Ollama brand tile icon.
src/MeshWeaver.Graph/Icons/azure.svg Adds Azure brand tile icon.
src/MeshWeaver.Documentation/Data/WhatsNew/2026-07-10-model-provider-icons.md Documents the picker change in “What’s New”.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +149 to +151
// Brand logo when the provider name resolves to a known maker; the
// generic key otherwise. See ModelProviderIcons.
Icon = ModelProviderIcons.ForProvider(source.ProviderName) ?? "Key",
Comment on lines +199 to +201
// Brand logo inferred from the model id (then provider) so a model reads
// as its maker; the generic sparkle when unknown. See ModelProviderIcons.
Icon = ModelProviderIcons.ForModel(source.ProviderName, modelId) ?? "Sparkle",
@github-actions

github-actions Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

884 tests  ±0   700 ✅ ±0   2m 53s ⏱️ +21s
  8 suites ±0   184 💤 ±0 
  8 files   ±0     0 ❌ ±0 

Results for commit 7044244. ± Comparison against base commit 1373ae5.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

731 tests  ±0   729 ✅ ±0   2m 6s ⏱️ +9s
 10 suites ±0     2 💤 ±0 
 10 files   ±0     0 ❌ ±0 

Results for commit 7044244. ± Comparison against base commit 1373ae5.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

  9 files  ± 0    9 suites  ±0   3m 8s ⏱️ +38s
929 tests  - 14  929 ✅  - 14  0 💤 ±0  0 ❌ ±0 
938 runs   - 14  938 ✅  - 14  0 💤 ±0  0 ❌ ±0 

Results for commit 7044244. ± Comparison against base commit 1373ae5.

This pull request removes 41 and adds 27 tests. Note that renamed tests count towards both.
MeshWeaver.Persistence.Test.ConcurrentRequestsTest ‑ ConcurrentRequests_MultipleNodeTypes_AllLoadWithoutHanging
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.AI.ModelProviderSett"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.Architecture.Deploym"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.Architecture.MacLoca"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.Architecture.NodeTyp"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.Architecture.QueryRe"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.DataMesh.PythonStand"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.DataMesh.md")
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.GUI.DisplayControls."···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.GUI.InputControls.md")
…
MeshWeaver.Persistence.Test.DataContextIntegrationTest ‑ GraphHub_InitializesWithConfiguration
MeshWeaver.Persistence.Test.DataContextIntegrationTest ‑ MeshNode_ChildrenAvailable_ViaPersistence
MeshWeaver.Persistence.Test.DataContextIntegrationTest ‑ Persistence_CanCreateNodeWithContent
MeshWeaver.Persistence.Test.DataContextIntegrationTest ‑ Persistence_CanUpdateNodeWithContent
MeshWeaver.Persistence.Test.DataContextIntegrationTest ‑ Persistence_StoryContentIsPreserved
MeshWeaver.Persistence.Test.FileSystemChangeWatcherTests ‑ ExternalFileCreation_NotifiesObservers
MeshWeaver.Persistence.Test.FileSystemChangeWatcherTests ‑ ExternalFileCreation_ObserveQueryReceivesUpdate
MeshWeaver.Persistence.Test.FileSystemChangeWatcherTests ‑ ExternalFileDeletion_NotifiesObservers
MeshWeaver.Persistence.Test.FileSystemChangeWatcherTests ‑ ExternalFileDeletion_ObserveQueryReceivesRemoval
MeshWeaver.Persistence.Test.FileSystemChangeWatcherTests ‑ ExternalFileModification_NotifiesObservers
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

1 042 tests  ±0   1 042 ✅ ±0   3m 0s ⏱️ -11s
   10 suites ±0       0 💤 ±0 
   10 files   ±0       0 ❌ ±0 

Results for commit 7044244. ± Comparison against base commit 1373ae5.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

983 tests  ±0   979 ✅ ±0   6m 5s ⏱️ +15s
 11 suites ±0     4 💤 ±0 
 11 files   ±0     0 ❌ ±0 

Results for commit 7044244. ± Comparison against base commit 1373ae5.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

834 tests  +315   833 ✅ +314   5m 45s ⏱️ + 2m 38s
 10 suites +  1     1 💤 +  1 
 10 files   +  1     0 ❌ ±  0 

Results for commit 7044244. ± Comparison against base commit 1373ae5.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   58 files  +  1     58 suites  +1   22m 58s ⏱️ + 3m 49s
5 403 tests +301  5 212 ✅ +300  191 💤 +1  0 ❌ ±0 
5 412 runs  +301  5 221 ✅ +300  191 💤 +1  0 ❌ ±0 

Results for commit 7044244. ± Comparison against base commit 1373ae5.

This pull request removes 41 and adds 342 tests. Note that renamed tests count towards both.
MeshWeaver.Persistence.Test.ConcurrentRequestsTest ‑ ConcurrentRequests_MultipleNodeTypes_AllLoadWithoutHanging
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.AI.ModelProviderSett"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.Architecture.Deploym"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.Architecture.MacLoca"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.Architecture.NodeTyp"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.Architecture.QueryRe"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.DataMesh.PythonStand"···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.DataMesh.md")
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.GUI.DisplayControls."···)
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.GUI.InputControls.md")
…
MeshWeaver.Hosting.Monolith.Test.AgentChatClientNoSuitableAgentTest ‑ AgentChatClient_OnHostedSubHub_DoesNotReturn_NoSuitableAgent
MeshWeaver.Hosting.Monolith.Test.AgentPickerProjectionPartitionedTest ‑ PartitionedPath_ObserveAgents_FromHostedSubHub_PopulatesCombobox
MeshWeaver.Hosting.Monolith.Test.AgentPickerProjectionPartitionedTest ‑ PartitionedPath_ObserveAgents_PopulatesCombobox
MeshWeaver.Hosting.Monolith.Test.AgentPickerProjectionPartitionedTest ‑ PartitionedPath_ObserveModels_PopulatesCombobox
MeshWeaver.Hosting.Monolith.Test.AgentPickerProjectionTest ‑ ObserveAgents_FromHostedSubHub_PopulatesCombobox
MeshWeaver.Hosting.Monolith.Test.AgentPickerProjectionTest ‑ ObserveAgents_FromMeshHub_PopulatesCombobox
MeshWeaver.Hosting.Monolith.Test.AgentPickerProjectionTest ‑ ObserveAgents_SpaceAndUserSet_SurfacesSpaceAgent_UserAgent_AndPlatform
MeshWeaver.Hosting.Monolith.Test.AgentPickerProjectionTest ‑ ObserveModels_FromHostedSubHub_PopulatesCombobox
MeshWeaver.Hosting.Monolith.Test.AgentPickerProjectionTest ‑ ObserveModels_FromMeshHub_PopulatesCombobox
MeshWeaver.Hosting.Monolith.Test.AgentSyncedQueryFromHostedHubTest ‑ HostedSubHub_GetQuery_AgentDropdownNamesAreNonEmpty
…

♻️ This comment has been updated with latest results.

Addresses Copilot review: MeshNodeImageHelper filters out legacy Fluent
icon names (Key/Sparkle), so fall back to the /static/NodeTypeIcons/*.svg
URL directly, keeping node.Icon always renderable. No rendered change (the
legacy names already resolved to those same SVGs via the NodeType default).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rbuergi

rbuergi commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @copilot. Addressed the first point in 7044244 — the fallbacks now use the self-contained /static/NodeTypeIcons/{key,sparkle}.svg URLs instead of the legacy Fluent names.

On the second point (retrofit): correct — because Provider/{name} nodes are seeded ExcludeThisAndChildren, the new logos apply to fresh installs / partitions / local+test, but existing deployed catalogs keep their current icons until a one-time repair patches Icon on the existing Provider + child model nodes. Tracking that as a follow-up rather than bundling a migration into this PR.

@rbuergi
rbuergi merged commit 8073c1e into main Jul 10, 2026
16 checks passed
rbuergi added a commit that referenced this pull request Aug 6, 2026
…the hub's core

Found while scanning src/ for hand-woven async. MessageHub carried a whole debug
tracing rig, introduced with #407 (2026-07-10) for the teardown-SIGSEGV hunt and never
removed:

  static readonly bool    DisposeTraceEnabled      // env var
  static readonly string  DisposeTraceLogPath      // a file in the temp dir
  static readonly Channel<string>? DisposeTraceChannel
  static MessageHub() { _ = Task.Run(async () => ...append to file forever...); }

Three problems, in the most load-bearing class in the repo:

* A static mutable Channel<string> is exactly the process-wide state AGENTS.md
  forbids — it survives every mesh disposal and is shared by every hub in the process.
* `_ = Task.Run(...)` in a static constructor is an unobserved, never-cancelled
  background loop that lives for the lifetime of the process.
* It is debug instrumentation shipped in production code, writing to a temp file.

Removed entirely — fields, static ctor, the DisposeTrace method and all nine call
sites. Nothing outside MessageHub.cs referenced it.

No diagnostic is lost. Five of the nine call sites sat directly beside a
logger/TryLog line carrying the same information; those are simply gone. The three in
the hosted-dispose path had NO logging of their own, so they become real log lines
rather than disappearing — and the error arm is now a Warning WITH the exception,
where it previously recorded a hosted-hub disposal fault only into an off-by-default
trace file, i.e. effectively nowhere. That needed a TryLog overload taking an
Exception so the stack trace survives instead of being flattened into the message.

Also dropped the now-dead workflow plumbing that collected the trace file
(dotnet-test, alc-unload-probe, flake-repro).

No log LEVELS were changed: the added lines are Debug for phase boundaries (not
shipped to Loki) and Warning for the fault.

MeshWeaver.Messaging.Hub.Test: 153/153. Solution builds clean under
-c Release -p:CIRun=true -warnaserror.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hx6BiUYBu5U2Zdnom4Uz4X
@rbuergi
rbuergi deleted the feat/model-provider-icons branch August 7, 2026 07:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants